/**
 * 
 */
package com.onewingu.gcs.server;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gdata.client.appsforyourdomain.UserService;
import com.google.gdata.client.authn.oauth.GoogleOAuthHelper;
import com.google.gdata.client.authn.oauth.GoogleOAuthParameters;
import com.google.gdata.client.authn.oauth.OAuthHmacSha1Signer;
import com.google.gdata.client.contacts.ContactsService;
import com.google.gdata.data.Link;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.appsforyourdomain.provisioning.UserEntry;
import com.google.gdata.data.appsforyourdomain.provisioning.UserFeed;
import com.google.gdata.data.contacts.ContactEntry;
import com.google.gdata.data.contacts.ContactFeed;
import com.google.gdata.data.contacts.ContactGroupEntry;
import com.google.gdata.data.contacts.ContactGroupFeed;
import com.google.gdata.data.contacts.GroupMembershipInfo;
import com.google.gdata.util.AuthenticationException;

/**
 * @author er66
 *
 */
public class Sync2 {
	//cs = contact share
	//public static final String PREFIX = "CS-";

	public static final String SOURCE_ACCT_EMAIL = "erin.reddick@freedomreg.com";

	public static final String APP_NAME = "Contact Share";

	public static final String APPS_FEEDS_URL_BASE = "https://apps-apis.google.com/a/feeds/";

	public static final String SERVICE_VERSION = "2.0";

	public static final String DOMAIN = "freedomreg.com";
	
	public static final String OAUTH_CALLBACK = "http://onewingu-gcontactshare.appspot.com/upgradeToken.jsp";
	public static final String CONTACTS_SCOPE = "https://www.google.com/m8/feeds/contacts/default/full";
	public static final String CONSUMER_KEY = "onewingu-gcontactshare.appspot.com";
	public static final String CONSUMER_SECRET = "wXDf2McCoAsFRfV44iMpy25p";

	private PersistenceManager pm;
	
	public Sync2(PersistenceManager pm) {
		this.pm = pm;
	}
	
	public void sync() throws Exception {
		Logger log = Logger.getLogger(Sync2.class.getName());
		System.out.println("sys");
		log.info("log");
		
		//PrintWriter writer = resp.getWriter();
		String domainUrlBase = APPS_FEEDS_URL_BASE + DOMAIN + "/";

		//oauth
		GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
		oauthParameters.setOAuthConsumerKey(CONSUMER_KEY);
		oauthParameters.setOAuthConsumerSecret(CONSUMER_SECRET);
		oauthParameters.setScope(CONTACTS_SCOPE);
		oauthParameters.setOAuthCallback(OAUTH_CALLBACK);
		
		GoogleOAuthHelper oauthHelper = new GoogleOAuthHelper(new OAuthHmacSha1Signer());
		oauthHelper.getUnauthorizedRequestToken(oauthParameters);
		//end oauth
		
		//load the source account
		//PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(SourceAccount.class);
		List<SourceAccount> results = (List<SourceAccount>)query.execute();
		System.out.println("source account count: " + results.size());
		log.info("source account count: " + results.size());
		if(results.size() == 0) {
			System.out.println("No source account found. Cannot continue.");
			return;
		}
		
		SourceAccount sa = results.get(0);

		String saUsername = sa.getUsername();
		String saPassword = null;
		//try {
			saPassword = Encrypter.decrypt(sa.getEncryptedPassword());
		/*} catch(Exception e) {
			System.out.println("Error: " + e.getMessage());
			return;
		}*/

		UserService userService = new UserService(APP_NAME);
		//try {
		System.out.println("set credentials: " + saUsername + " / ");
		userService.setUserCredentials(saUsername, saPassword);
		/*} catch (AuthenticationException ae) {
			System.out.println("authentication exception<br />" + ae.getMessage() + "<br />");
			return;
		}*/

		saUsername = saUsername.substring(0, saUsername.indexOf('@'));
		
		//get map of groups of source account
		Map<String, ContactGroupEntry> saGroupMap = null;
		//try {
			saGroupMap = getGroupMap(saUsername);
		/*} catch(Exception e) {
			System.out.println("error reading map of groups of sa: " + e.getMessage());
			return;
		}*/

		//get map of contacts of source account
		Map<String, ContactEntry> saContactMap = null;
		//try {
		saContactMap = getContactMap(saUsername);
		/*} catch(Exception e) {
			System.out.println("error reading map of contacts of sa: " + e.getMessage());
			return;
		}*/

		//load shared groups
		//pm = PMF.get().getPersistenceManager();
		Map<String, SharedGroup> sharedGroupMap = loadSharedGroups();
		
		//get all domain users
		URL retrieveUrl = new URL(domainUrlBase + "user/" + SERVICE_VERSION + "/");
		
		UserFeed allUsers = new UserFeed();
		UserFeed currentPage;
		Link nextLink;

		//try {
			do {
				currentPage = userService.getFeed(retrieveUrl, UserFeed.class);
				allUsers.getEntries().addAll(currentPage.getEntries());
				nextLink = currentPage.getLink(Link.Rel.NEXT, Link.Type.ATOM);
				if (nextLink != null) {
					retrieveUrl = new URL(nextLink.getHref());
				}
			} while (nextLink != null);
		/*} catch(Exception e) {
			throw new IOException(e);
		}*/

		for(UserEntry user : allUsers.getEntries()) {
			/*writer.print("user: " + user.toString() + "<br />");
			writer.print("title: " + user.getTitle().getPlainText() + "<br />");
			writer.print("name: " + user.getName() + "<br />");
			writer.print("email: " + user.getEmail() + "<br />");
			writer.print("username: " + user.getLogin().getUserName() + "<br />");
			writer.print("password: " + user.getLogin().getPassword() + "<br />");
			writer.print("<br />");*/
		}
		
		//algorithm

		loop1(sharedGroupMap, saGroupMap, saContactMap);
		
		//try {
			loop2(saUsername, sharedGroupMap.values(), saContactMap, saGroupMap, allUsers.getEntries());
		/*} catch(Exception e) {
			System.out.println("exception caught in loop2: " + e.getMessage());
			return;
		}*/

		//try {
			loop3(saUsername, sharedGroupMap.values(), saContactMap, saGroupMap, allUsers.getEntries());
		/*} catch(Exception e) {
			System.out.println("exception caught in loop3: " + e.getMessage());
			return;
		}*/
		
		try {
			for(SharedGroup sharedGroup : sharedGroupMap.values()) {
				sharedGroup.preSave();
				pm.makePersistent(sharedGroup);
			}
		/*} catch(Exception e) {
			System.out.println("exception caught persisting shared groups: " + e.getMessage());*/
		} finally {
			//pm.close();
		}
	}

	public ContactFeed getContactFeed(String username) throws Exception {
		ContactsService contactsService = new ContactsService("Contact Share");
		TokenStore ts = new TokenStore();

		//load and set auth token
		String authToken = ts.getToken(username);
		contactsService.setAuthSubToken(authToken);

		URL feedUrl = new URL("https://www.google.com/m8/feeds/contacts/default/full");
		ContactFeed resultFeed = contactsService.getFeed(feedUrl, ContactFeed.class);
		return resultFeed;
	}

	public ContactGroupFeed getGroupFeed(String username) throws Exception {
		ContactsService contactsService = new ContactsService("Contact Share");
		TokenStore ts = new TokenStore();

		//load and set auth token
		String authToken = ts.getToken(username);
		contactsService.setAuthSubToken(authToken);

		URL feedUrl = new URL("https://www.google.com/m8/feeds/groups/default/full");
		ContactGroupFeed resultFeed = contactsService.getFeed(feedUrl, ContactGroupFeed.class);
		return resultFeed;
	}

	private Map<String, ContactEntry> getContactMap(String username) throws Exception {
		ContactFeed feed = getContactFeed(username);
		Map<String, ContactEntry> contactMap = new HashMap<String, ContactEntry>();
		for(ContactEntry contact : feed.getEntries()) {
			String contactID = contact.getId();
			contactMap.put(contactID, contact);
		}
		return contactMap;
	}
	
	private Map<String, ContactGroupEntry> getGroupMap(String username) throws Exception {
		ContactGroupFeed feed = getGroupFeed(username);
		Map<String, ContactGroupEntry> groupMap = new HashMap<String, ContactGroupEntry>();
		for(ContactGroupEntry group : feed.getEntries()) {
			if(group.hasSystemGroup())
				continue;
			String groupID = group.getId();
			groupMap.put(groupID, group);
		}
		return groupMap;
	}
	
	private Map<String, SharedGroup> loadSharedGroups() {
		Map<String, SharedGroup> sharedGroupMap = new HashMap<String, SharedGroup>();
		
		//PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(SharedGroup.class);
		List<SharedGroup> results = (List<SharedGroup>)query.execute();
		
		for(SharedGroup sharedGroup : results) {
			sharedGroup.postLoad();
			sharedGroupMap.put(sharedGroup.getSaGroupId(), sharedGroup);
		}
		
		return sharedGroupMap;
	}
	
	private void copyContactData(ContactEntry fromContact, ContactEntry toContact) {
		toContact.setBirthday(fromContact.getBirthday());
		toContact.setGender(fromContact.getGender());
		toContact.setInitials(fromContact.getInitials());
		toContact.setMaidenName(fromContact.getMaidenName());
		toContact.setMileage(fromContact.getMileage());
		toContact.setName(fromContact.getName());
		toContact.setNickname(fromContact.getNickname());
		toContact.setOccupation(fromContact.getOccupation());
		toContact.setPriority(fromContact.getPriority());
		toContact.setSensitivity(fromContact.getSensitivity());
		toContact.setShortName(fromContact.getShortName());
		toContact.setSubject(fromContact.getSubject());
		toContact.setTitle(fromContact.getTitle());
		toContact.setWhere(fromContact.getWhere());
		
		//toContact.addCalendarLink(calendarLink);
		//toContact.addEmailAddress(emailAddress);
		//toContact.addHobby(hobby);
		//toContact.addImAddress(imAddress);
		//toContact.addJot(jot);
		//toContact.addLanguage(language);
		//toContact.addOrganization(organization);
		//toContact.addPhoneNumber(phoneNumber);
		//toContact.addPostalAddress(postalAddress);
		//toContact.addRelation(relation);
		//toContact.addStructuredPostalAddress(structuredPostalAddress);
		//toContact.addUserDefinedField(userDefinedField);
		//toContact.addWebsite(website);
	}
	
	private void loop1(Map<String, SharedGroup> sharedGroupMap, Map<String, ContactGroupEntry> saGroupMap,
			Map<String, ContactEntry> saContactMap) {
		
		System.out.println("<br /><br />loop1<br />");
		
		//build map of sourceContactID -> set of sourceGroupIDs
		Map<String, Set<String>> saContactGroupMap =
			new HashMap<String, Set<String>>();
		
		for(ContactEntry contact : saContactMap.values()) {
			System.out.println("contact- " + contact.getId() + " :: " + contact.getName() + "<br />");
			List<GroupMembershipInfo> gmis = contact.getGroupMembershipInfos();
			Set<String> groupSet = new HashSet<String>();
			for(GroupMembershipInfo gmi : gmis) {
				System.out.println("gmi-href: " + gmi.getHref() + "<br />");
				//ContactGroupEntry group = sourceGroupMap.get(gmi.getHref());
				groupSet.add(gmi.getHref());
			}
			
			saContactGroupMap.put(contact.getId(), groupSet);
		}
		
		for(SharedGroup sharedGroup : sharedGroupMap.values()) {
			//if db:g:c not in sa then mark db:g to be deleted
			if(!(saGroupMap.containsKey(sharedGroup.getSaGroupId()))) {
				sharedGroup.setDeleteFlag(true);
			} else {
				for(SharedContact sharedContact : sharedGroup.getContactMap().values()) {
					//if saContact was deleted, then update sharedGroup
					if(!(saContactMap.containsKey(sharedContact.getSaContactID()))) {
						sharedContact.setDeleteFlag(true);
					} else {
						//if saContact was removed from group, then mark sharedContact for deletion
						Set<String> saContactGroupIdSet = saContactGroupMap.get(sharedContact.getSaContactID());
						if(!(saContactGroupIdSet.contains(sharedGroup.getSaGroupId()))) {
							sharedContact.setDeleteFlag(true);
						} else {						
							ContactEntry contact = saContactMap.get(sharedContact.getSaContactID());
							sharedContact.setUpdateTime(contact.getUpdated());
						}
					}
					
				}
			}
			
			for(String contactId : saContactGroupMap.keySet()) {
				ContactEntry saContact = saContactMap.get(contactId);
				Set<String> saContactGroups = saContactGroupMap.get(saContact.getId());
				
				if(!(saContactGroups.contains(sharedGroup.getSaGroupId())))
					continue;
				
				if(!(sharedGroup.getContactMap().keySet().contains(contactId))) {
					SharedContact newSharedContact = new SharedContact(contactId, saContact.getUpdated());
					sharedGroup.getContactMap().put(contactId, newSharedContact);
				}
			}
		}
		
	}
	
	private void loop2(String saUsername, Collection<SharedGroup> sharedGroups,
			Map<String, ContactEntry> saContactMap, Map<String, ContactGroupEntry> saGroupMap,
			Collection<UserEntry> users)
	throws Exception {
		
		for(SharedGroup sharedGroup : sharedGroups) {
			String saGroupId = sharedGroup.getSaGroupId();
			Map<String, SharedContact> sharedContactMap = sharedGroup.getContactMap();
			//ContactGroupEntry saGroup = saGroupMap.get(groupId);
			
			for(UserEntry user : users) {
				String username = user.getLogin().getUserName();
				if(username.equals(saUsername))
					continue;
				
				Map<String, ContactEntry> contactMap = null;
				Map<String, ContactGroupEntry> groupMap = null;
				
				try {
					contactMap = getContactMap(username);
					groupMap = getGroupMap(username);
				} catch(Exception e) {
					System.out.println("error with " + username + ": " + e);
					continue;
				}

				//build map of userContactID -> set of userGroupIDs
				Map<String, Set<String>> contactGroupMap =
					new HashMap<String, Set<String>>();
				
				for(ContactEntry contact : contactMap.values()) {
					//System.out.println("contact- " + contact.getId() + " :: " + contact.getName() + "<br />");
					List<GroupMembershipInfo> gmis = contact.getGroupMembershipInfos();
					Set<String> groupSet = new HashSet<String>();
					for(GroupMembershipInfo gmi : gmis) {
						//System.out.println("gmi-href: " + gmi.getHref() + "<br />");
						//ContactGroupEntry group = sourceGroupMap.get(gmi.getHref());
						groupSet.add(gmi.getHref());
					}
					
					contactGroupMap.put(contact.getId(), groupSet);
				}

				String userGroupId = sharedGroup.getGroupMap().get(username);

				if(sharedGroup.isDeleteFlag()) {
					if(groupMap.containsKey(saGroupId)) {
						ContactGroupEntry group = groupMap.get(saGroupId);
						//TODO don't delete the group, just stop sharing it
						//group.delete();
						continue;
					}
				}
				
				for(SharedContact sharedContact : sharedGroup.getContactMap().values()) {
					String saContactId = sharedContact.getSaContactID();
					Map<String, String> userContactMap = sharedContact.getUserContactMap();
					if(!(userContactMap.containsKey(username)))
						continue;
					String userContactId = userContactMap.get(username);

					if(!(contactMap.containsKey(userContactId)))
						continue;
					
					ContactEntry contact = contactMap.get(userContactId);
					
					if(sharedContact.isDeleteFlag()) {
						//TODO don't delete the contact, just remove it from the group
						//contact.delete();
						for(GroupMembershipInfo gmi : contact.getGroupMembershipInfos()) {
							if(gmi.getHref().equalsIgnoreCase(userGroupId)) {
								gmi.setDeleted(true);
								contact.update();
								break;
							}
						}
						
						continue;
					}
					
					if(contact.getUpdated().compareTo(sharedContact.getUpdateTime()) > 0) {
						ContactEntry saContact = saContactMap.get(saContactId);
						copyContactData(contact, saContact);
						saContact.update();
						sharedContact.setUpdateTime(contact.getUpdated());
					}
				}
				
				for(ContactEntry contact : contactMap.values()) {
					String contactId = contact.getId();
					List<GroupMembershipInfo> gmis = contact.getGroupMembershipInfos();
					for(GroupMembershipInfo gmi : gmis) {
						if(gmi.getHref().equals(saGroupId)) {
							boolean matchingSaContactExists = false;
							for(String saContactId : sharedContactMap.keySet()) {
								SharedContact sharedContact = sharedContactMap.get(saContactId);
								String checkContactId = sharedContact.getUserContactMap().get(username);
								if(contactId.equals(checkContactId)) {
									matchingSaContactExists = true;
									break;
								}
							}
							if(!matchingSaContactExists) {
								ContactEntry newSaContact = new ContactEntry();
								copyContactData(contact, newSaContact);
								newSaContact = insertContactEntry(saUsername, newSaContact);
								SharedContact newSharedContact = new SharedContact(newSaContact.getId(),
										contact.getUpdated());
								newSharedContact.getUserContactMap().put(username, contactId);
								sharedContactMap.put(newSaContact.getId(), newSharedContact);
							}
							break;
						}
					}
				}
				
			}
		}
	}

	private void loop3(String saUsername, Collection<SharedGroup> sharedGroups,
			Map<String, ContactEntry> saContactMap, Map<String, ContactGroupEntry> saGroupMap,
			Collection<UserEntry> users) throws Exception {
		
		Set<SharedGroup> sharedGroupsToDelete = new HashSet<SharedGroup>();
		for(SharedGroup sharedGroup : sharedGroups) {
			if(sharedGroup.isDeleteFlag()) {
				sharedGroupsToDelete.add(sharedGroup);
			}
		}
		sharedGroups.removeAll(sharedGroupsToDelete);

		for(SharedGroup sharedGroup : sharedGroups) {
			String saGroupId = sharedGroup.getSaGroupId();
			//ContactGroupEntry saGroup = saGroupMap.get(groupId);
			String groupTitle = saGroupMap.get(saGroupId).getTitle().getPlainText();
			
			Set<SharedContact> sharedContactsToDelete = new HashSet<SharedContact>();
			for(SharedContact sharedContact : sharedGroup.getContactMap().values()) {
				if(sharedContact.isDeleteFlag()) {
					sharedContactsToDelete.add(sharedContact);
				}
			}
			for(SharedContact sharedContactToDelete : sharedContactsToDelete) {
				sharedGroup.getContactMap().remove(sharedContactToDelete.getSaContactID());
			}

			for(UserEntry user : users) {
				String username = user.getLogin().getUserName();
				if(username.equals(saUsername))
					continue;

				Map<String, ContactEntry> contactMap = null;
				Map<String, ContactGroupEntry> groupMap = null;
				
				try {
					contactMap = getContactMap(username);
					groupMap = getGroupMap(username);
				} catch(Exception e) {
					System.out.println("error with " + username + ": " + e);
					continue;
				}
				
				if(!(groupMap.containsKey(sharedGroup.getGroupMap().get(username)))) {
					ContactGroupEntry newGroup = new ContactGroupEntry();
					newGroup.setTitle(new PlainTextConstruct(groupTitle));
					newGroup = insertContactGroupEntry(username, newGroup);
					sharedGroup.getGroupMap().put(username, newGroup.getId());
				}
								
				for(SharedContact sharedContact : sharedGroup.getContactMap().values()) {
					String saContactId = sharedContact.getSaContactID();
					ContactEntry saContact = saContactMap.get(saContactId);
					Map<String, String> sharedUserContactMap = sharedContact.getUserContactMap();
					
					if(!(sharedUserContactMap.containsKey(username))) {
						ContactEntry newContact = new ContactEntry();
						copyContactData(saContact, newContact);
						
						//put the new contact into the group
						GroupMembershipInfo gmi = new GroupMembershipInfo(false, sharedGroup.getGroupMap().get(username));
						newContact.addGroupMembershipInfo(gmi);
						
						newContact = insertContactEntry(username, newContact);
						sharedUserContactMap.put(username, newContact.getId());
					} else {
						String userContactId = sharedUserContactMap.get(username);
						//need to make sure userContactId still exists
						if(!(contactMap.containsKey(userContactId))) {
							ContactEntry newContact = new ContactEntry();
							copyContactData(saContact, newContact);
							newContact = insertContactEntry(username, newContact);
							sharedUserContactMap.put(username, newContact.getId());
						} else {
							ContactEntry userContact = contactMap.get(userContactId);
							if(sharedContact.getUpdateTime().compareTo(userContact.getUpdated()) > 0) {
								copyContactData(saContact, userContact);
								userContact.update();
							}
						}
					}
				}
			}
		}
	}
	
	private ContactEntry insertContactEntry(String username, ContactEntry contact) throws Exception {
		ContactsService contactsService = new ContactsService("Contact Share");
		TokenStore ts = new TokenStore();

		//load and set auth token
		String authToken = ts.getToken(username);
		contactsService.setAuthSubToken(authToken);

		URL serviceUrl = new URL("https://www.google.com/m8/feeds/contacts/default/full");
		return contactsService.insert(serviceUrl, contact);
	}

	private ContactGroupEntry insertContactGroupEntry(String username, ContactGroupEntry group)
	throws Exception {
		ContactsService contactsService = new ContactsService("Contact Share");
		TokenStore ts = new TokenStore();

		//load and set auth token
		String authToken = ts.getToken(username);
		contactsService.setAuthSubToken(authToken);

		URL serviceUrl = new URL("https://www.google.com/m8/feeds/groups/default/full");
		return contactsService.insert(serviceUrl, group);
	}

}
